﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ComponentModel;

using Noris.Tools.TraceVisualiser.Components;
using Noris.Tools.TraceVisualiser.Support;

namespace Noris.Tools.TraceVisualiser.TraceData
{
    /// <summary>
    /// Vizuální panel, který zobrazuje graf TraceData, navázaný na dokument TraceDocument.
    /// </summary>
    internal class TraceDocumentPanel : DPanel
    {
		#region Konstrukce, property
		public TraceDocumentPanel()
		{
			this._Init();
		}
		/// <summary>
		/// Hlavička dokumentu = soubor "Projekt", vlastnosti projektu. Setování způsobí načtení dat dokumentu a datovou inicializaci.
		/// </summary>
        [EditorBrowsable( EditorBrowsableState.Never)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public TraceDocument Document { get { return this._Document; } set { this._DocumentSet(value); } }
		private TraceDocument _Document;
		/// <summary>
		/// Zdroj trace dat, komunikuje s grafem, z datového dokumentu dostává zadání (umístění souborů a parametry zpracování).
		/// </summary>
        [EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public TraceDataSource DataSource { get; private set; }
		/// <summary>
		/// Zobrazovací jednotka - graf třídy DColGrid. Je napojen na datový zdroj DataSource
		/// </summary>
		public DColGrid Graph { get; private set; }
        /// <summary>
        /// Správce fronty UndoRedo
        /// </summary>
        public UndoRedoCls UndoRedo { get; private set; }
		/// <summary>
		/// Obecně typovaný datový zdroj, pro přístup k vlastnostem skrytým za explicitní implementací interface IDataSource
		/// </summary>
		public IDataSource IDataSource { get { return (IDataSource)this.DataSource; } }
        /// <summary>
        /// Disposable
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            if (this.Graph != null)
                this.Graph.Dispose();
            
            base.Dispose(disposing);

            // Hodně se toho disposovalo, je třeba vyčistit paměť a ukázat to uživateli:
            Steward.MemoryGcFinish();
        }
		#endregion
		#region Inicializace
		/// <summary>
		/// Nevizuální inicializace datového zdroje a grafu. Ještě ne dokument (projekt) a inicializace dat.
		/// </summary>
		private void _Init()
		{
			this.DataSource = new TraceDataSource();
			this.Graph = new DColGrid();
            this.UndoRedo = new UndoRedoCls(5);

			// Deklarace grafu:
			List<GraphDeclarationCls> gd = this.IDataSource.GraphDeclarations;
			// Tady dojde k propojení grafu a datového zdroje, a to i z hlediska předání delegátů na procesy na asynchronní úrovni:
			if (gd.Count > 0)
				this.Graph.GraphDeclarationData = gd[0];

			this.Graph.Dock = DockStyle.Fill;

			this.Controls.Add(this.Graph);
			this.CurrentTooltipMode = TooltipMode.Baloon;

            this._RegisterEvents();
		}
		#endregion
        #region Eventy - transparentní
        /// <summary>
        /// Event, který bude vyvolán při změnách zobrazeného času na časové ose
        /// </summary>
        [Browsable(true)]
        [Category("Action")]
        [Description("Event, který bude vyvolán při změnách zobrazeného času na časové ose")]
        public event TimeAxisChangeHandler TimeAxisChange;
        /// <summary>
        /// Event, který bude vyvolán při změnách v registru Undo/Redo
        /// </summary>
        [Browsable(true)]
        [Category("Action")]
        [Description("Event, který bude vyvolán při změnách v registru Undo/Redo")]
        public event EventHandler UndoRedoChange;
        /// <summary>
        /// Zaregistruje svoje eventhandlery do eventů svých objektů
        /// </summary>
        private void _RegisterEvents()
        {
            this.Graph.TimeAxisChange += this._TimeAxisChange;
            this.UndoRedo.Change += new EventHandler(_UndoRedo_Change);
        }
        /// <summary>
        /// Předání eventu TimeAxisChange z this.Graph do nadřízené vrstvy
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _TimeAxisChange(object sender, TimeAxisChangeEventArgs e)
        {
            this._TimeAxisChangeToUndoRedo(e);
            if (this.TimeAxisChange != null)
                this.TimeAxisChange(this, e);
        }
		public override void Refresh()
		{
			if (this.Graph != null) this.Graph.Refresh();
			base.Refresh();
		}
        #endregion
        #region UndoRedo
        /// <summary>
        /// Registrace změny TimeRange pro účely UndoRedo registru. Reaguje pouze na finální změnu času, čas vkádá do Undo Redo registru.
        /// </summary>
        /// <param name="e"></param>
        private void _TimeAxisChangeToUndoRedo(TimeAxisChangeEventArgs e)
        {
            if (e.FinalChange)
                this.UndoRedo.AddNewItem(e.TimeRange);      // Vyvolá změnu => event _UndoRedo_Change()
        }
        /// <summary>
        /// Eventhandler po změně v registru Undo/Redo
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _UndoRedo_Change(object sender, EventArgs e)
        {
            if (this.UndoRedoChange != null)
                this.UndoRedoChange(this, e);
        }
		/// <summary>
		/// Vyvolá provedení akce Undo/Redo
		/// </summary>
		/// <param name="actionType"></param>
		public void UndoRedoAction(UndoRedoActionType actionType)
		{
			object action = null;
			bool hasAction = false;
			switch (actionType)
			{
				case UndoRedoActionType.Undo:
					hasAction = this.UndoRedo.TryGetUndoItem(out action);
					break;
				case UndoRedoActionType.Redo:
					hasAction = this.UndoRedo.TryGetRedoItem(out action);
					break;
			}
			if (hasAction && action != null)
				this.UndoRedoExecAction(action);
		}

		internal void UndoRedoExecAction(object action)
		{
			// A protože provedení akce (typicky změna času na časové ose) by standardně vyvolala zápis do Undo/Redo zásobníku,
			//  musíme to provést "pod pokličkou":
			using (var hat = this.UndoRedo.GetHat())
			{
				if (action.GetType() == typeof(TimeRange))
				{
					TimeRange timeRange = (TimeRange)action;
					this.Graph.CurrentTimeRange = timeRange;
				}
			}
		}
		#endregion
		#region Vložení připraveného dokumentu, který se zde má zobrazovat. Datová inicializace.
		/// <summary>
		/// Vložení připraveného dokumentu, který se zde má zobrazovat
		/// </summary>
		/// <param name="document"></param>
		private void _DocumentSet(TraceDocument document)
		{
            this._Document = document;
            if (document != null)
            {
                this.Graph.LayoutImportFrom(document.DocumentLayout, false);
            }
			this.IDataSource.DataChanged += new DataSourceEventHandler(_DataSource_DataChanged);    // Registrace eventhandleru pro událost Změna dat
			DataRequestInitialize request = new DataRequestInitialize(this.IDataSource.RunRequest, this._DataSourceRequestDone, this.Graph.GraphDeclarationData, TimeRange.Empty, document);
			request.AddRequestAsync();
		}
		/// <summary>
		/// Obsluha události, kdy datový zdroj sám detekoval změnu dat, a oznamuje to vizuální vrstvě
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		void _DataSource_DataChanged(object sender, DataSourceEventArgs args)
		{
			this.Graph.DataSourceDataChanged(sender, args);
		}
		/// <summary>
		/// Metoda po dokončení inicializace datového zdroje.
		/// Pozor: tato metoda je volaná v threadu AsyncWorker = na pozadí.
		/// Řízení se zde nesmí zdržet.
		/// A už vůbec se v tomto threadu nesmí volat další requesty.
		/// Typicky se provede invokace GUI threadu, a z něj lze vydávat další požadavky do datového zdroje přes AsyncWorker.
		/// </summary>
		/// <param name="request"></param>
		private void _DataSourceRequestDone(IDataSourceRequest request)
		{
			if (this.InvokeRequired)
				// Zde jsem v threadu AsyncWork, ten musím co nejdříve opustit:
				this.BeginInvoke(new Action<IDataSourceRequest>(_DataSourceRequestDone), request);
			else
			{	// zde jsem v threadu na popředí (GUI):
				switch (request.RequestType)
				{
					case DataSourceRequestType.Initialize:
						this._DataSourceInitializeDone(request as DataRequestInitialize);
						break;
				}
			}
		}
		private void _DataSourceInitializeDone(DataRequestInitialize request)
		{
			DataRequestInitialize requestInit = request as DataRequestInitialize;
			if (requestInit == null) return;

			if (!request.InitialTimeRange.IsEmpty)
				this.Graph.CurrentTimeRange = request.InitialTimeRange;

			if (request.ReloadAllRows)
				this.Graph.ReadFirstDataList(GID.Empty);
		}
		#endregion
	}
}
